package com.yunxin.core.domain.service.order.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.multi.ListValueMap;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yunxin.common.utils.JacksonUtils;
import com.yunxin.core.application.dto.order.BalanceProduct;
import com.yunxin.core.application.service.TeamService;
import com.yunxin.core.domain.external.product.ProductAllocationDetail;
import com.yunxin.core.domain.model.order.OrderUserApplyAllocationRepository;
import com.yunxin.core.domain.model.team.Team;
import com.yunxin.core.domain.model.weight.SysTeamWeight;
import com.yunxin.core.infra.common.order.enums.OrderLockKey;
import com.yunxin.core.infra.common.product.enums.ProductAbutmentTypeEnum;
import com.yunxin.core.infra.persistence.mapper.team.SysTeamMapper;
import com.yunxin.core.infra.persistence.mapper.weight.SysTeamWeightMapper;
import com.yunxin.core.utils.infra.support.lock.DistributedLock;
import com.yunxin.core.utils.infra.support.lock.DistributedLockFactory;
import com.yunxin.core.utils.infra.utils.DateFormatUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zero°
 * @since 2023-06-28
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OrderUserApplyAllocationStockHandle {

    private static final String PRODUCT_STOCK_KEY = "core:order:allocation:product:stock:";

    private static final String MERCHANT_STOCK_KEY = "core:order:merchant:stock:";
    private static final String LAST_ORDER_PRODUCT = "core:order:last:product:";

    private final RedisTemplate redisTemplate;

    private final DistributedLockFactory distributedLockFactory;

    private final OrderUserApplyAllocationRepository orderUserApplyAllocationRepository;

    private final SysTeamMapper sysTeamMapper;

    private final SysTeamWeightMapper sysTeamWeightMapper;


    private static final String BALANCE_STACK = """
            local json_str = ARGV[1]; -- 产品对象集合 有产品id,控量,num=是已经发了几个了
            local new_products=cjson.decode(cjson.decode(json_str));
            local red_products=redis.call('lrange',KEYS[1],0,-1); --获取旧的进量数据
            local new_products_list={}
            for k,v in ipairs(new_products) do
            	local item = cjson.decode(v);
            	item.num=0;
            	new_products_list[#new_products_list + 1]=item;	
            end

            -- 复制一份
            local oldProduct= {};
            local products = {};
            local red_products_list={};

            --原有的产品

            if red_products then\s
            	for rk,rv in ipairs(red_products) do
            		local rproduct=cjson.decode(rv);
            		red_products_list[#red_products_list+1]=rproduct;
            		products[#products+1]=rproduct;
            	end
            end 	

            -- 找出新的 加入products
            -- 找出本轮待发的产品  例如red_products_list=E,C,D,A new_products_list=E,C,D 待发就是 ECD
            local commitProducts={};
            if #red_products_list >0 then\s
            	for nk,nv in ipairs(new_products_list) do
            		local isAlreadyInProducts = false;
            		for rk,rv in ipairs(red_products_list) do
            			 local rid =tonumber(rv.productId);
            			 local nid =tonumber(nv.productId);
            			 if rid == nid then
            				isAlreadyInProducts = true;
            				commitProducts[#commitProducts+1]=rv;
            			 end
            		end
            		if not isAlreadyInProducts then
            		  nv.num=0;
            		  products[#products + 1]=nv;
            		  commitProducts[#commitProducts+1]=nv;
            		end
            	end
            else\s
            products=new_products_list;	
            commitProducts=new_products_list;
            end\s

             -- 总控量
            local totalLimitNum=0;
            for nk,nv in ipairs(commitProducts) do
            	totalLimitNum = totalLimitNum+tonumber(nv.totalLimit);
            end\s

            -- 倒序 待发的产品
            table.sort(commitProducts, function(a, b)
                return tonumber(a.totalLimit) > tonumber(b.totalLimit)
            end);


            -- 求出平均数
            local avgNum=math.ceil(totalLimitNum/#commitProducts) ;

            -- 找出合适的产品分发
            local suitableProductId=nil;
            for k,v in ipairs(commitProducts) do
            	
            	local limitNum=tonumber(v.totalLimit);
            	-- 小于平均就是1 大于就是总数除以平均数 向上取整
            	local max=1;
            	if limitNum > avgNum then\s
            		max=math.ceil(limitNum/avgNum) ;
            	end\s
            	if not v.num then\s
            		v.num=0
            	end
            	
            	-- 如果suitableProductId是空的 且当前产品可以继续发
            	if not suitableProductId  and  tonumber(v.num) < max then	
            		v.num=v.num+1	
            		suitableProductId =v.productId;
            	end

            end\s

            -- 没有说明 有的产品发完一轮了或者全部发完了 就要把他还原
            if not suitableProductId then\s
            	for k,v in ipairs(commitProducts) do
            		v.num=0;
            	end
            	suitableProductId=commitProducts[1].productId;
            	commitProducts[1].num=1;
            end\s

            redis.call('del',KEYS[1]);
            for k,v in ipairs(products) do
            	redis.call('rpush',KEYS[1],cjson.encode(v));
            end

            return suitableProductId;
            """;

    /**
     * 判断产品库存
     * @param productId productId
     * @param productStock productStock
     * @return boolean
     */
    public boolean checkProductStock(Long productId, Integer productStock) {
        ValueOperations<String, Integer> operations = redisTemplate.opsForValue();
        Integer count = operations.get(PRODUCT_STOCK_KEY + productId);
        if (Objects.isNull(count)) {
            count = initOrderProductCount(productId);
        }
        //产品订单数量小于产品库存
        if (count!=-1 && count < productStock) {
            return true;
        } else {
            //库存不足
            log.warn("产品库存不足,productId={}", productId);
            return false;
        }
    }


    /**
     * 增加产品库存
     * @param productId productId
     * @return boolean
     */
    public void determineProductStock(Long productId) {
        //产品订单数量+1
        redisTemplate.opsForValue().increment(PRODUCT_STOCK_KEY + productId, 1);
    }

    /**
     * 增加商户库存
     * @param productId productId
     * @return boolean
     */
    public void determineMerchantStock(Long productId) {
        //产品订单数量+1
        redisTemplate.opsForValue().increment(MERCHANT_STOCK_KEY + productId, 1);
    }

    /**
     * 释放产品库存
     * @param productId productId
     */
    public void releaseProductStock(Long productId){
        //产品订单数量-1
        redisTemplate.opsForValue().increment(PRODUCT_STOCK_KEY + productId, -1);
    }

    /**
     * 初始化当天产品订单数量
     * @param productId productId
     * @return productStock(int)
     */
    private Integer initOrderProductCount(Long productId) {

        DistributedLock lock = distributedLockFactory.provideDistributedLock(OrderLockKey.ORDER_STACK_INIT_KEY.getPrefix());

        if (lock.tryLock(OrderLockKey.ORDER_STACK_INIT_KEY.getWaitTime(), OrderLockKey.ORDER_STACK_INIT_KEY.getReleaseTime(), OrderLockKey.ORDER_STACK_INIT_KEY.getTimeUnit())) {
            try {
                ValueOperations<String, Integer> operations = redisTemplate.opsForValue();
                Integer count = operations.get(PRODUCT_STOCK_KEY + productId);
                if (Objects.isNull(count)) {
                    Date productEndTime = new Date();
                    Date productStartTime = DateFormatUtils.X.getDateByStartDate(productEndTime);
                    Integer productOrderCount = orderUserApplyAllocationRepository.getAllocationOrderCountByProductId(productId, productStartTime, productEndTime);
                    if (Objects.isNull(productOrderCount)) {
                        productOrderCount = 0;
                    }
                    Date now = new Date();
                    Date endTime = DateFormatUtils.X.getDateByEndDate(now);
                    // 减去1秒,避时间超出24:00:00(因为后续程序执行需要时间)
                    long time = endTime.getTime() - now.getTime() - 1000;
                    operations.set(PRODUCT_STOCK_KEY + productId, productOrderCount, time, TimeUnit.MILLISECONDS);
                    return productOrderCount;
                }
                return count;
            } finally {
                lock.unlock();
            }
        }
        //返回负一的几率非常非常小 不考虑影响
        return -1;
    }


    /**
     * 获取最新分配单产品id(Hash结构)
     * @param filed  字段
     * @return productId
     */
    public Long getLastOrderProductId(String filed) {
        HashOperations<String, String, Long> operations = redisTemplate.opsForHash();
        Object value = operations.get(LAST_ORDER_PRODUCT, filed);
        if (Objects.nonNull(value)) {
            if (value instanceof Long l) {
                return l;
            }
            return Long.parseLong(value.toString());
        }
        return 0L;
    }

    /**
     * 更新最新分配单产品id(Hash结构)
     * @param filed  hash表字段
     * @param productId 产品id
     */
    public void updateLastOrderProductId(String filed, Long productId) {
        HashOperations<String, String, Long> operations = redisTemplate.opsForHash();
        operations.put(LAST_ORDER_PRODUCT, filed, productId);

    }

    /**
     * 判断商户产品库存
     * @param merchantId
     * @return
     */
    public boolean checkTeamProductStock(Long merchantId) {
        // 1.获取该商户下面所有团队的当日接收总量
        List<Integer> teamIds = sysTeamMapper.selectList(new QueryWrapper<Team>()
                .eq("merchant_id", merchantId)
                .eq("status", 1)
                .eq("is_del", 1)).stream().map(Team::getId).toList();
        // 如果商户没有团队,则标识该产品是api产品 直接返回
        if(teamIds.isEmpty()){
            return true;
        }
        long count = sysTeamWeightMapper.selectList(new QueryWrapper<SysTeamWeight>()
                .in("team_id", teamIds)
                .eq("is_del", SysTeamWeight.ON)
                .eq("status", SysTeamWeight.ON))
                .stream().map(SysTeamWeight::getTotalLimit).count();
        // 商户接收产品总量是0，直接返回
        if(count==0){
            return false;
        }
        // 获取总消耗
        ValueOperations<String, Integer> operations = redisTemplate.opsForValue();
        Integer consume = operations.get(MERCHANT_STOCK_KEY + merchantId);

        if (Objects.isNull(consume)) {
            consume = initOrderMerchantCount(merchantId);
        }
        //产品订单数量小于产品库存
        if (consume!=-1 && consume < count) {
            return true;
        } else {
            //库存不足
            log.warn("商户产品库存不足,merchantId={}", merchantId);
            return false;
        }

    }

    /**
     * 初始化当天商户产品订单数量
     * @param merchantId
     * @return
     */
    private Integer initOrderMerchantCount(Long merchantId) {
        DistributedLock lock = distributedLockFactory.provideDistributedLock(OrderLockKey.ORDER_MERCHANT_STACK_INIT_KEY.getPrefix());

        if (lock.tryLock(OrderLockKey.ORDER_MERCHANT_STACK_INIT_KEY.getWaitTime(), OrderLockKey.ORDER_MERCHANT_STACK_INIT_KEY.getReleaseTime(), OrderLockKey.ORDER_MERCHANT_STACK_INIT_KEY.getTimeUnit())) {
            try {
                ValueOperations<String, Integer> operations = redisTemplate.opsForValue();
                Integer count = operations.get(MERCHANT_STOCK_KEY + merchantId);
                if (Objects.isNull(count)) {
                    Date merchantEndTime = new Date();
                    Date merchantStartTime = DateFormatUtils.X.getDateByStartDate(merchantEndTime);
                    // 据商户id获取时间内分配订单条数
                    Integer merchantOrderCount = orderUserApplyAllocationRepository.getAllocationOrderCountByMerchantId(merchantId, merchantStartTime, merchantEndTime);
                    if (Objects.isNull(merchantOrderCount)) {
                        merchantOrderCount = 0;
                    }
                    Date now = new Date();
                    Date endTime = DateFormatUtils.X.getDateByEndDate(now);
                    // 减去1秒,避时间超出24:00:00(因为后续程序执行需要时间)
                    long time = endTime.getTime() - now.getTime() - 1000;
                    operations.set(MERCHANT_STOCK_KEY + merchantId, merchantOrderCount, time, TimeUnit.MILLISECONDS);
                    return merchantOrderCount;
                }
                return count;
            } finally {
                lock.unlock();
            }
        }
        //返回负一的几率非常非常小 不考虑影响
        return -1;
    }



    /**
     * 产品 控量分发
     * @param weightProducts
     * @param channelPromotionCode
     * @return
     */
    public ProductAllocationDetail balanceStack3(List<ProductAllocationDetail> weightProducts, String channelPromotionCode) {

        String dateStr = DateUtil.date().toString(DatePattern.NORM_DATE_PATTERN);
        StringBuilder builder = new StringBuilder(dateStr);
        builder.append("_").append(channelPromotionCode);




        ListValueMap<Integer, BalanceProduct> productBalanceMap = new ListValueMap<>();
        Map<Long, ProductAllocationDetail> detailMap = weightProducts.stream().collect(Collectors.toMap(ProductAllocationDetail::getProductId, Function.identity()));

        //优先发api
        for (ProductAllocationDetail weightProduct : weightProducts) {
            BalanceProduct balanceProduct = new BalanceProduct(weightProduct.getProductId(), weightProduct.getTotalLimit());
            if (ProductAbutmentTypeEnum.API.getValue().equals(weightProduct.getAbutmentType())) {
                productBalanceMap.putValue(weightProduct.getAbutmentType(), balanceProduct);
            } else {
                productBalanceMap.putValue(Integer.valueOf(2), balanceProduct);
            }
        }

        List<BalanceProduct> balanceProducts = null;
        List<BalanceProduct> apiBalanceProducts = productBalanceMap.get(ProductAbutmentTypeEnum.API.getValue());
        if (CollUtil.isNotEmpty(apiBalanceProducts)) {
            balanceProducts = apiBalanceProducts;
            builder.append("_api");
        } else {
            balanceProducts = productBalanceMap.get(2);
            builder.append("_self");
        }

        builder.append("_").append(weightProducts.get(0).getChargedPrice());
        builder.append("_").append(weightProducts.get(0).getWeight());

        Object[] v = new Object[balanceProducts.size()];

        for (int i = 0; i < balanceProducts.size(); i++) {
            BalanceProduct balanceProduct = balanceProducts.get(i);
            v[i] = JacksonUtils.X.format(balanceProduct);
        }

        String key = builder.toString();
        Object[] values = new Object[1];
        values[0] = JacksonUtils.X.format(v);


        try {
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<Long>(BALANCE_STACK,Long.class);
            redisScript.setResultType(Long.class);
            Object execute = redisTemplate.execute(redisScript, List.of(key), values);

            Long productId = Long.valueOf(execute.toString());
            return detailMap.get(productId);
        } catch (Exception e) {
            log.error("控量分发异常", e);
            if (weightProducts.size() == 1) {
                return weightProducts.get(0);
            }
            ThreadLocalRandom current = ThreadLocalRandom.current();
            return weightProducts.get(current.nextInt(0, weightProducts.size()));
        }


    }
}
